﻿using CID.Domain.Development;
using CID.Domain.Infrastructure;
using CID.Domain.Operations;
using CID.Domain.Users;
using CID.Execution.Support.Contracts;
using CID.Execution.Support.DeploymentInitiation;
using CID.Execution.Support.PackageRegistration;
using CID.Tests.Support;
using NHibernate;

namespace CID.Execution.Controller.Tests
{
    class BaseControllerTests : BasePersistenceTests
    {
        #region DomainData Repositories
        protected class UsersDataRepository
        {
            public IUser Operator { get; private set; }
            public IUser Developer { get; private set; }
            public IUser Manager { get; private set; }
            public IUser Architect { get; private set; }
            public IUser EnvironmentManager { get; private set; }
            public IUser ComponentManager { get; private set; }
            public IUser ReleaseManager { get; private set; }
            public IUser Roleless { get; private set; }

            public UsersDataRepository(IUser operatorUser, IUser developer, IUser manager, IUser architect, 
                IUser environmentManager, IUser componentManager, IUser releaseManager, IUser roleless)
            {
                Operator = operatorUser;
                Developer = developer;
                Manager = manager;
                Architect = architect;
                EnvironmentManager = environmentManager;
                ComponentManager = componentManager;
                ReleaseManager = releaseManager;
                Roleless = roleless;
            }
        }

        protected sealed class OperationsDataRepository
        {
            public IServerRole ServerRole1 { get; private set; }
            public IServerRole ServerRole2 { get; private set; }
            public IServerRole ServerRole3 { get; private set; }
            public IServerRole ServerRole4 { get; private set; }
            public IServer Server1 { get; private set; }
            public IServer Server2 { get; private set; }
            public IServer Server3 { get; private set; }
            public IServer Server4 { get; private set; }
            public IServer Server5 { get; private set; }
            public IServer Server6 { get; private set; }
            public IEnvironment Environment { get; private set; }
            public ILocation Location1 { get; private set; }
            public ILocation Location2 { get; private set; }

            public OperationsDataRepository(IServerRole serverRole1, IServerRole serverRole2, IServerRole serverRole3, IServerRole serverRole4,
                IServer server1, IServer server2, IServer server3, IServer server4, IServer server5, IServer server6,
                IEnvironment environment, ILocation location1, ILocation location2)
            {
                ServerRole1 = serverRole1;
                ServerRole2 = serverRole2;
                ServerRole3 = serverRole3;
                ServerRole4 = serverRole4;
                Server1 = server1;
                Server2 = server2;
                Server3 = server3;
                Server4 = server4;
                Server5 = server5;
                Server6 = server6;
                Environment = environment;
                Location1 = location1;
                Location2 = location2;
            }

        }

        protected sealed class InfrastructureDataRepository
        {
            public IDeploymentAgent DeploymentAgent { get; private set; }
            public IPackageType PackageType { get; private set; }
            public ITemplateType TemplateType { get; private set; }
            public IActivationTemplate ActivationTemplate { get; private set; }
            public IDeactivationTemplate DeactivationTemplate { get; private set; }
            public IDeploymentTemplate DeploymentTemplate { get; private set; }

            public InfrastructureDataRepository(IDeploymentAgent deploymentAgent)
            {
                DeploymentAgent = deploymentAgent;
            }

            public InfrastructureDataRepository(IDeploymentAgent deploymentAgent, IPackageType packageType, ITemplateType templateType,
                IActivationTemplate activationTemplate, IDeactivationTemplate deactivationTemplate, IDeploymentTemplate deploymentTemplate)
                : this(deploymentAgent)
            {
                PackageType = packageType;
                TemplateType = templateType;
                ActivationTemplate = activationTemplate;
                DeactivationTemplate = deactivationTemplate;
                DeploymentTemplate = deploymentTemplate;
            }
        }

        protected sealed class DevelopmentDataRepository
        {
            public IComponent[] Components { get; private set; }
            public IRelease ReleasedRelease { get; private set; }
            public IRelease ActiveRelease { get; private set; }
            public IRelease InvalidRelease { get; private set; }

            public DevelopmentDataRepository(IComponent[] components, IRelease releasedRelease, IRelease activeRelease, IRelease invalidRelease)
            {
                Components = components;
                ReleasedRelease = releasedRelease;
                ActiveRelease = activeRelease;
                InvalidRelease = invalidRelease;
            }
        }
        #endregion

        protected static UsersDataRepository UsersData { get; private set; }
        protected static OperationsDataRepository OperationsData { get; private set; }
        protected static InfrastructureDataRepository InfrastructureData { get; private set; }
        protected static DevelopmentDataRepository DevelopmentData { get; private set; }
        private static InfrastructureDataRepository infrastructureDataPartial;

        protected override void ConfigureNails(NailsFramework.Config.INailsConfigurator nails)
        {
            nails.IoC.Container<NailsFramework.IoC.Unity>()
                .Persistence.DataMapper<NailsFramework.Persistence.NHibernate>(CID.Domain.Persistence.Configuration.Configure)
                .UserInterface.Platform<NailsFramework.UserInterface.Mvc>()
                .Logging.Logger<NailsFramework.Logging.Log4net>()
                .IoC.Lemming<NailsFramework.UnitOfWork.Session.WebExecutionContext>(x => x.Ignore())
                .IoC.Lemming<NailsFramework.UnitOfWork.Session.CallExecutionContext>()
                .InspectAssemblyOf<IComponent>()
                .InspectAssemblyOf<Controller>()
                .Aspects.ApplyBehavior<NailsFramework.UnitOfWork.UnitOfWorkBehavior>().ToInheritorsOf<IControllerService>()
                .Aspects.ApplyBehavior<NailsFramework.UnitOfWork.UnitOfWorkBehavior>().ToInheritorsOf<IDeploymentInitiator>()
                .Aspects.ApplyBehavior<NailsFramework.UnitOfWork.UnitOfWorkBehavior>().ToInheritorsOf<IPackageRegistrator>()
                .Initialize();
        }

        protected override void SetupData()
        {
            base.SetupData();
            var session = OpenSession();

            CreateUsersData(session);

            CreateInfrastractureDataPartial(session);

            CreateOperationsData(session);

            CreateInfrastructureData(session);

            CreateDevelopmentData(session);

            CleanSession(session);
        }

        private void CreateUsersData(ISession session)
        {
            var architectUser = new User("Architect User", "Architect.User", "architect.user@somedomain.com");
            architectUser.AssignRole(UserRole.Architect);
            session.SaveOrUpdate(architectUser);

            var managerUser = new User("Manager User", "Manager.User", "manager.user@somedomain.com");
            managerUser.AssignRole(UserRole.Manager);
            session.SaveOrUpdate(managerUser);

            var componentManagerUser = new User("ComponentManager User", "ComponentManager.User", "componentmanager.user@somedomain.com");
            componentManagerUser.AssignRole(UserRole.ComponentManager);
            session.SaveOrUpdate(componentManagerUser);

            var releaseManagerUser = new User("ReleaseManager User", "ReleaseManager.User", "releasemanager.user@somedomain.com");
            releaseManagerUser.AssignRole(UserRole.ReleaseManager);
            session.SaveOrUpdate(releaseManagerUser);

            var environmentManagerUser = new User("EnvironmentManager User", "EnvironmentManager.User", "environmentmanager.user@somedomain.com");
            environmentManagerUser.AssignRole(UserRole.EnvironmentManager);
            session.SaveOrUpdate(environmentManagerUser);

            var developerUser = new User("Developer User", "Developer.User", "developer.user@somedomain.com");
            developerUser.AssignRole(UserRole.Developer);
            session.SaveOrUpdate(developerUser);

            var operatorUser = new User("Operator User", "Operator.User", "operator.user@somedomain.com");
            operatorUser.AssignRole(UserRole.Operator);
            session.SaveOrUpdate(operatorUser);

            var rolelessUser = new User("Roleless User", "Roleless.User", "roleless.user@somedomain.com");
            session.SaveOrUpdate(rolelessUser);
            
            UsersData = new UsersDataRepository(operatorUser, developerUser, managerUser, architectUser, 
                environmentManagerUser, componentManagerUser, releaseManagerUser, rolelessUser);
        }

        private void CreateInfrastractureDataPartial(ISession session)
        {
            var deploymentAgent = new DeploymentAgent("The Deployment Agent", "localhost:8000");
            session.SaveOrUpdate(deploymentAgent);

            infrastructureDataPartial = new InfrastructureDataRepository(deploymentAgent);
        }

        private void CreateOperationsData(ISession session)
        {
            var environment = new Environment("The Environment", "Environment Description", UsersData.EnvironmentManager);
            session.SaveOrUpdate(environment);

            var serverRole1 = new ServerRole("Sever Role One", "The Server Role One");
            session.SaveOrUpdate(serverRole1);
            var serverRole2 = new ServerRole("Sever Role Two", "The Server Role Two");
            session.SaveOrUpdate(serverRole2);
            var serverRole3 = new ServerRole("Sever Role Three", "The Server Role Three");
            session.SaveOrUpdate(serverRole3);
            var serverRole4 = new ServerRole("Sever Role Four", "The Server Role Four");
            session.SaveOrUpdate(serverRole4);

            var location1 = new Location("Location One", "The Location One", infrastructureDataPartial.DeploymentAgent);
            session.SaveOrUpdate(location1);
            var location2 = new Location("Location Two", "The Location Two", infrastructureDataPartial.DeploymentAgent);
            session.SaveOrUpdate(location2);

            var server1 = CreateServer("Sever One", "The Server One", location1);
            server1.Assign(environment, serverRole1);
            session.SaveOrUpdate(server1);
            var server2 = CreateServer("Sever Two", "The Server Two", location2);
            server2.Assign(environment, serverRole2);
            session.SaveOrUpdate(server2);
            var server3 = CreateServer("Sever Three", "The Server Three", location1);
            server3.Assign(environment, serverRole3);
            session.SaveOrUpdate(server3);
            var server4 = CreateServer("Sever Four", "The Server Four", location1);
            server4.Assign(environment, serverRole3);
            session.SaveOrUpdate(server4);
            var server5 = CreateServer("Sever Five", "The Server Five", location2);
            server5.Assign(environment, serverRole4);
            session.SaveOrUpdate(server5);
            var server6 = CreateServer("Sever Six", "The Server Six", location2);
            server6.Assign(environment, serverRole4);
            session.SaveOrUpdate(server6);

            OperationsData = new OperationsDataRepository(serverRole1, serverRole2, serverRole3, serverRole4,
                server1, server2, server3, server4, server5, server6, environment, location1, location2);
        }

        private static Server CreateServer(string name, string description, ILocation location)
        {
            var result = new Server(name, description, location);
            location.AddServer(result);
            return result;
        }

        private void CreateInfrastructureData(ISession session)
        {
            var deploymentAgent = infrastructureDataPartial.DeploymentAgent;

            var packageType = new PackageType("Package Type", "The Package Type");
            session.SaveOrUpdate(packageType);

            var templateType = new TemplateType("Template Type", "The Template Type", "", "");
            session.SaveOrUpdate(templateType);

            var activationTemplate = templateType.CreateActivationTemplate("Activation Template", "The Activation Template", new byte[0]);
            session.SaveOrUpdate(activationTemplate);
            var deactivationTemplate = templateType.CreateDeactivationTemplate("Deactivation Template", "The Deactivation Template", new byte[0]);
            session.SaveOrUpdate(deactivationTemplate);
            var deploymentTemplate = templateType.CreateDeploymentTemplate("Deployment Template", "The Deployment Template", new byte[0]);
            session.SaveOrUpdate(deploymentTemplate);

            InfrastructureData = new InfrastructureDataRepository(deploymentAgent, packageType, templateType, activationTemplate, deactivationTemplate, deploymentTemplate);
        }

        private void CreateDevelopmentData(ISession session)
        {

            var components = new Component[]{
                new Component("Component 0", "The Component 0", OperationsData.ServerRole1, UsersData.ComponentManager),
                new Component("Component 1", "The Component 1", OperationsData.ServerRole2, UsersData.ComponentManager),
                new Component("Component 2", "The Component 2", OperationsData.ServerRole2, UsersData.ComponentManager),
                new Component("Component 3", "The Component 3", OperationsData.ServerRole3, UsersData.ComponentManager),
                new Component("Component 4", "The Component 4", OperationsData.ServerRole3, UsersData.ComponentManager),
                new Component("Component 5", "The Component 5", OperationsData.ServerRole4, UsersData.ComponentManager),
                new Component("Component 6", "The Component 6", OperationsData.ServerRole4, UsersData.ComponentManager)};
            foreach (var component in components)
            {
                component.ActivationDeactivationDefinition.ActivationTemplate = InfrastructureData.ActivationTemplate;
                component.ActivationDeactivationDefinition.DeactivationTemplate = InfrastructureData.DeactivationTemplate;
                session.SaveOrUpdate(component);
            }

            var releasedRelease = new Release("Release 1", "The Release 1", UsersData.ReleaseManager);
            session.SaveOrUpdate(releasedRelease);
            var activeRelease = new Release("Release 2", "The Release 2", UsersData.ReleaseManager);
            session.SaveOrUpdate(activeRelease);
            var invalidRelease = new Release("Release 3", "The Release 3", UsersData.ReleaseManager);
            session.SaveOrUpdate(invalidRelease);

            DevelopmentData = new DevelopmentDataRepository(components, releasedRelease, activeRelease, invalidRelease);
            SetUpVersionsAndReleases(components, releasedRelease, activeRelease, invalidRelease, session);
        }

        private void SetUpVersionsAndReleases(IComponent[] components, IRelease releasedRelease, IRelease activeRelease, IRelease invalidRelease, ISession session)
        {
            foreach (var component in components)
                CreateVersion(component, releasedRelease, session);

            for (int i = 0; i < 3; i++)
                CreateVersion(components[i], activeRelease, session);

            CreateVersion(components[5], invalidRelease, session);
            CreateVersion(components[6], invalidRelease, session, false);

            foreach (var version in components[0].Versions)
                version.AddDependency(components[4]);

            foreach (var version in components[2].Versions)
                version.AddDependency(components[1]);

            foreach (var version in components[3].Versions)
                version.AddDependency(components[0]);

            foreach (var version in components[5].Versions)
                version.AddDependency(components[1]);

            foreach (var version in components[6].Versions)
                version.AddDependency(components[5]);

            releasedRelease.SetActive();
            OperationsData.Environment.RegisterReleaseDeployment(releasedRelease);
            releasedRelease.SetReleased();
            activeRelease.SetActive();
            invalidRelease.SetActive();
        }

        private IVersion CreateVersion(IComponent component, IRelease release, ISession session, bool createPackage=true)
        {
            string versionName = string.Format("{0}'s version for {1}", component.Name, release.Name);
            var result = component.CreateVersion(versionName, "The " + versionName);
            release.AddVersion(result);
            result.DeploymentDefinition.DeploymentTemplate = InfrastructureData.DeploymentTemplate;
            session.Save(result);
            if (createPackage)
            {
                InfrastructureData.PackageType.CreatePackage(result, new byte[0], "Package for " + versionName, true);
                session.Save(result.ActivePackage);
            }
            return result;
        }
    }
}
